package pl;

import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.MessageDigest;
import javacard.security.RandomData;

public class KeyEvolutionEngine {

	//private static final byte OFFSET_0 = (byte) 0;
	private static final short MAX_RND_LEN = (short) 32;
	private static final short MAX_SIZE = (short) 256;

	/* input data */
	byte[] key;
	short kLen;

	/* presignature ~ input message */
	byte[] preS;
	short psLen;

	/* random */
	private byte[] random;
	short rLen;

	/* output data */
	byte[] nextKey;
	byte[] resultHmac;
	short hmacLen;

	/* temp buffer */
	byte[] shared;
	short sharLen;

	boolean finalized;

	private HMAC hmac;
	private KDF2BytesGenerator kdf2;
	private MessageDigest hashEngine;
	private RandomData rndData;
	boolean isInitialized;

	public KeyEvolutionEngine() {

		nextKey = new byte[MAX_SIZE];
		key = new byte[MAX_SIZE];
		resultHmac = new byte[MAX_SIZE];

		shared = new byte[(short) 544];
		hmac = new HMAC();
		kdf2 = new KDF2BytesGenerator();
		rndData = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM);
		random = new byte[MAX_RND_LEN];
		finalized = true;

	}

	void init(MessageDigest mdInstance, byte[] initKey, short keyLen) {

		this.hashEngine = mdInstance;
		this.hmacLen = this.hashEngine.getLength();

		Util.arrayCopy(initKey, (short) 0, key, (short) 0, keyLen);
		Util.arrayCopy(initKey, (short) 0, nextKey, (short) 0, keyLen);

		this.kLen = keyLen;

		hmac.init(mdInstance, key, keyLen);
		
		finalized = true;
	}

	public void FollowKeyEvo(byte[] preSignature, short presigOff,
			short presigLen, byte[] random, short rndLen) {

		if (rndLen > MAX_RND_LEN)
			ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

		sharLen = 0;

		// ++ 0x80 - 128b
		Util.arrayCopy(key, (short) 0, shared, sharLen, kLen);
		sharLen += kLen;

		// ++ 0x80 - 128b
		Util.arrayCopy(preSignature, presigOff, shared, sharLen, presigLen);
		sharLen += presigLen;

		// ++ 0x20 - 32b
		Util.arrayCopy(random, (short) 0, shared, sharLen, rndLen);
		sharLen += rndLen;

		kdf2.init(hashEngine, shared, sharLen, null, (short) 0);

		JCSystem.beginTransaction();

		kdf2.generateBytes(nextKey, (short) 0, hmacLen, true);

		finalized = false;

		JCSystem.commitTransaction();

		if (kLen < this.hashEngine.getLength()) {
			this.kLen = (short) (this.hashEngine.getLength() + 1);
		}

		Util.arrayCopy(random, (short) 0, this.random, (short) 0, rndLen);
	}

	public void PerformKeyEvo(byte[] preSignature, short presigOff,
			short presigLen) {

		if (this.finalized == true) {
			
			rLen = MAX_RND_LEN;
			rndData.generateData(random, (short) 0, rLen);
			FollowKeyEvo(preSignature, presigOff, presigLen, random, rLen);

			/*rLen = MAX_RND_LEN;
			rndData.generateData(random, (short) 0, rLen);
			random = new byte[(short)32]; random[0] = 0x00; random[31] = 0x00;*/
			
			FollowKeyEvo(preSignature, presigOff, presigLen, random, rLen);


		 } else {
			 /* TODO: Exception finalized = false */
			 ISOException.throwIt((short) (ISO7816.SW_WRONG_LENGTH + 5));
		 }

	}

	public boolean verifyHmac(byte[] message, short off, short mLen,
			byte[] hMac, short hmacOff, short hmacLen) {

		hmac.init(this.hashEngine, nextKey, kLen);
		hmac.update(message, off, mLen);
		hmac.doFinal(resultHmac, (short) 0);

		byte result = Util.arrayCompare(resultHmac, (short) 0, hMac, hmacOff,
				hmacLen);

		if (result == 0)
			return true;

		return false;
	}

	private void createHmac(byte[] message, short off, short mLen, byte[] key,
			short keyLen, byte[] out, short outOff) {

		hmac.init(this.hashEngine, key, keyLen);
		hmac.update(message, off, mLen);
		hmac.doFinal(out, outOff);
	}

	public void createHmacWithNewKey(byte[] message, short off, short mLen,
			byte[] out, short outOff) {
		createHmac(message, off, mLen, this.nextKey, kLen, out, outOff);
	}

	public void createHmacWithOldKey(byte[] message, short off, short mLen,
			byte[] out, short outOff) {
		createHmac(message, off, mLen, this.key, kLen, out, outOff);
	}

	public byte[] getNextKey() {
		return this.nextKey;
	}

	public byte[] getRandom() {
		return random;
	}

	public boolean commitEvolutionStep() {
		
		JCSystem.beginTransaction();
		
		Util.arrayCopy(nextKey , (short) 0, key, (short) 0, kLen);
		this.finalized = true;
		
		JCSystem.commitTransaction();
		
		return true;
	}

	public boolean revertEvolutionStep() {
		this.finalized = true;
		return true;
	}
}
